﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using BakalarskaPraca.Generation.Data;
using BakalarskaPraca.Generation.Helpers;
using ColladaDotNet.Geometry;
using Microsoft.DirectX;

namespace BakalarskaPraca.Generation.Visualization
{
    public class GeometryVisualiser
    {
        public enum PerspectiveTypes
        {
            FlatXY = 0,
            FlatXZ = 1,
            FlatYZ = 2
        }

        private readonly List<ModelGeometry> _geometries;
        private readonly List<Vector3> _vertices;

        public PerspectiveTypes Perspective { get; set; }
        public Size Size { get; set; }
        public Color EdgeColor { get; set; }
        public Color FillColor { get; set; }
        public Color HighlightEdgeColor { get; set; }
        public Color HighlightFillColor { get; set; }

        private Bitmap _frame;
        private PointF _offset;
        private float _scale;
        private PointF _margin;

        private Bitmap _testBitmap;
        private Graphics _testGraphics;

        public void SetDefaultColors()
        {
            this.EdgeColor = Color.DarkBlue;
            this.FillColor = Color.FromArgb(10, Color.Gray);
            this.HighlightEdgeColor = Color.Red;
            this.HighlightFillColor = Color.FromArgb(128, Color.Yellow);
        }

        public GeometryVisualiser(List<ModelGeometry> geometries, Size size)
        {
            this._geometries = geometries;
            this._vertices = new List<Vector3>();
            geometries.ForEach(
                g => this._vertices.AddRange(g.Vertices));
            this.Size = size;
            this.SetDefaultColors();
            this.Perspective = PerspectiveTypes.FlatXY;
            this.UpdateSize(this.Size, this.Perspective);
            this.VisualizeScene();
            this._testBitmap = new Bitmap(1, 1);
            this._testGraphics = Graphics.FromImage(this._testBitmap);
        }

        public PointF GetRealPosition(int x, int y)
        {
            PointF result = new PointF();

            y = this._frame.Height - y;
              
            result.X = ((x - this._margin.X) / this._scale) - this._offset.X;
            result.Y = ((y - this._margin.Y) / this._scale) - this._offset.Y;              
            
            return result;
        }

        private static PointF Flatten(Vector3 vector, PerspectiveTypes perspective)
        {
            switch (perspective)
            {
                case PerspectiveTypes.FlatXY:
                    return new PointF(vector.X, vector.Y);
                case PerspectiveTypes.FlatXZ:
                    return new PointF(vector.X, vector.Z);
                case PerspectiveTypes.FlatYZ:
                    return new PointF(vector.Y, vector.Z);
                default:
                    throw new ArgumentOutOfRangeException("flattenStype");
            }
        }

        private PointF Flatten(Vector3 vector)
        {
            return Flatten(vector, this.Perspective);
        }

        public void UpdateSize(Size size, PerspectiveTypes perspective = PerspectiveTypes.FlatXY)
        {
            this.Size = size;
            this.Perspective = perspective;

            var bbox = new GeometryBoundingBox(this._vertices);
            PointF min = Flatten(bbox.MinVector);
            PointF max = Flatten(bbox.MaxVector);

            float sizeX = max.X - min.X;
            float xScale = (size.Width) / sizeX;
            float sizeY = max.Y - min.Y;
            float yScale = (size.Height) / sizeY;

            this._scale = Math.Min(xScale, yScale) * 0.98f;

            float maxSize = Math.Max(sizeX, sizeY);
            this._offset.X = (maxSize - sizeX) / 2 - min.X;
            this._offset.Y = (maxSize - sizeY) / 2 - min.Y;

            this._margin.X = (float)size.Width / 2 - (maxSize * this._scale) / 2;
            this._margin.Y = (float)size.Height / 2 - (maxSize * this._scale) / 2;
            this._frame = new Bitmap(this.Size.Width, this.Size.Height);
        }

        public Bitmap GetVisualization()
        {
            this.VisualizeScene();
            return this._frame;
        }

        public Bitmap GetCurrentFrame()
        {
            return this._frame;
        }

        public Bitmap GetHighlightedVisualization(ModelGeometry geometry)
        {
            this.HighlightGeometry(geometry);
            return this._frame;
        }

        public Bitmap GetHighlightedPolygonVisualization(Polygon polygon)
        {
            this.HighlightPolygon(polygon);
            return this._frame;
        }

        private void HighlightGeometry(ModelGeometry geometry)
        {
            Graphics canvas = Graphics.FromImage(this._frame);
            List<List<PointF>> visiblePolygons = new List<List<PointF>>();
            geometry.Polygons.ForEach(
                polygon =>
                visiblePolygons.Add(
                    polygon.Vertices.Select(
                        vertex =>
                        Flatten(
                            vertex.Positions[0].Vector)
                        ).ToList()
                ));

            visiblePolygons.ForEach(
                points =>
                    {
                        if (points.Count > 1)
                        {
                            var polygon = points.Select(
                                point =>
                                    {
                                        point.X = (point.X + this._offset.X) * this._scale + this._margin.X;
                                        point.Y = (point.Y + this._offset.Y) * this._scale + this._margin.Y;
                                        point.Y = this._frame.Height - point.Y;
                                        return point;
                                    }
                                );
                            canvas.FillPolygon(new SolidBrush(this.HighlightFillColor), polygon.ToArray());
                            canvas.DrawPolygon(new Pen(this.HighlightEdgeColor), polygon.ToArray());
                        }
                    });
        }
        
        private void HighlightPolygon(Polygon polygon)
        {
            Graphics canvas = Graphics.FromImage(this._frame);
            List<List<PointF>> visiblePolygons = new List<List<PointF>>();
            visiblePolygons.Add(
                polygon.Vertices.Select(
                    vertex =>
                    Flatten(
                        vertex.Positions[0].Vector)
                    ).ToList()
            );

            visiblePolygons.ForEach(
                points =>
                {
                    if (points.Count > 1)
                    {
                        var poly = points.Select(
                            point =>
                                {
                                    point.X = (point.X + this._offset.X) * this._scale + this._margin.X;
                                    point.Y = (point.Y + this._offset.Y) * this._scale + this._margin.Y;
                                    point.Y = this._frame.Height - point.Y;
                                    return point;
                                }
                            );
                        canvas.FillPolygon(new SolidBrush(this.HighlightFillColor), poly.ToArray());
                        canvas.DrawPolygon(new Pen(this.HighlightEdgeColor), poly.ToArray());
                    }
                });    
        }

        public bool IsPointInsidePolygon(int x, int y, Polygon polygon)
        {
            List<PointF> l = new List<PointF>(3);
            l.AddRange(
                polygon.Vertices.Select(
                    vertex =>
                    this.Flatten(
                        vertex.Positions[0].Vector)
                    )
            );
            var p = l.Select(
                point =>
                {
                    point.X = (point.X + this._offset.X) * this._scale + this._margin.X - x;
                    point.Y = (point.Y + this._offset.Y) * this._scale + this._margin.Y;
                    point.Y = this._frame.Height - point.Y - y;
                    return point;
                }
            ).ToArray();
            this._testGraphics.Clear(Color.White);
            this._testGraphics.FillPolygon(Brushes.Black, p);
            return this._testBitmap.GetPixel(0, 0).R == 0;
        } 


        private void VisualizeScene()
        {
            Graphics canvas = Graphics.FromImage(this._frame);
            canvas.Clear(Color.White);
            
            List<List<PointF>> visiblePolygons = new List<List<PointF>>();

            this._geometries.ForEach(
                geometry =>
                geometry.Polygons.ForEach(
                    polygon =>
                    visiblePolygons.Add(
                        polygon.Vertices.Select(
                            vertex =>
                            Flatten(
                                vertex.Positions[0].Vector)
                            ).ToList()
                    )));    

            visiblePolygons.ForEach(
                points =>
                    {
                        if (points.Count > 1)
                        {
                            var polygon = points.Select(
                                point =>
                                    {
                                        point.X = (point.X + this._offset.X) * this._scale + this._margin.X;
                                        point.Y = (point.Y + this._offset.Y) * this._scale + this._margin.Y;
                                        point.Y = this._frame.Height - point.Y; 
                                        return point;
                                    }
                                );
                            canvas.FillPolygon(new SolidBrush(this.FillColor), polygon.ToArray());
                            canvas.DrawPolygon(new Pen(this.EdgeColor), polygon.ToArray());
                        }
                    });
        }
    }
}
